import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:crypto/crypto.dart';

class CosManager {
  static final int signExpireTimeInSeconds = 10;

  String tmpSecretId;
  String tmpSecretKey;

  String sessionToken;

  String startTime;
  String expiredTime;
  String region;

  HttpClient httpClient;

  Function refreshTokenCallback;

  //刷新检查 Token
  bool _refreshToken() {
    if (this.sessionToken == "") {
      return false;
    }
    int currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
    if (currentTimestamp >= int.parse(expiredTime)) {
      print("Token 令牌过期了");
      refreshTokenCallback.call();
      return true;
    }
    return false;
  }

  //初始化配置信息
  Future<Null> init(Map<String, dynamic> config, Function callback) async {
    print("==========>>");
    this.refreshTokenCallback = callback;
    this.tmpSecretId = config['tmpSecretId'];
    this.tmpSecretKey = config['tmpSecretKey'];
    this.sessionToken = config['sessionToken'];
    this.startTime = config['startTime'];
    this.expiredTime = config['expiredTime'];
    this.region = config['region'];
    print(config);
    print("InitConfg<<==========");
    httpClient = _getClient();
  }

  //构建url
  String _buildUrl(String bucket, String region) {
    if (bucket == null || bucket == "") {
      throw new Exception("bucket is not null");
    }

    if (region == null || region == "") {
      throw new Exception("region is not null");
    }

    return "http://" + bucket + ".cos." + region + ".myqcloud.com";
  }

  HttpClient _getClient() {
    var httpClient = new HttpClient();
    //为charles抓包设置代理，为什么在代码中设置？请参考上篇文章。
    httpClient.findProxy = (url) {
      return HttpClient.findProxyFromEnvironment(url, environment: {
        "http_proxy": 'http://192.168.0.107:8888',
      });
    };
    return httpClient;
  }

  //设置公告请求头
  Map<String, String> _getPublicHeader() {
    return {};
  }

  //下载文件
  Future<Stream> getObject(String bucket, String fileKey) async {
    if (_refreshToken()) {
      return null;
    }
    var requestUrl = _buildUrl(bucket, region) + fileKey;
    var getPublicHeader = _getPublicHeader();

    getPublicHeader["Authorization"] =
        getAuth("GET", fileKey, getPublicHeader, null);
    if (this.sessionToken != "") {
      getPublicHeader["x-cos-security-token"] = this.sessionToken;
    }

    HttpClientRequest request = await httpClient.getUrl(Uri.parse(requestUrl));
    Iterable<MapEntry<String, String>> entries = getPublicHeader.entries;
    for (MapEntry<String, String> entry in entries) {
      request.headers.add(entry.key, entry.value);
    }

    if (this.sessionToken != "") {
      request.headers.add("x-cos-security-token", this.sessionToken);
    }
    HttpClientResponse response = await request.close();
    if (response.statusCode == 200) {
      print("下载文件成功【" + fileKey + "】");
      return response.distinct();
    } else {
      throw new Exception("下载文件失败");
    }
  }

  //上传文件
  Future<String> simpleUploadFile(
      String bucket, String filePathName, String cosPath) async {
    if (_refreshToken()) {
      return "";
    }
    var requestUrl = _buildUrl(bucket, region) + cosPath;

    Map<String, String> publicHeader = _getPublicHeader();
    publicHeader["Authorization"] = getAuth("PUT", cosPath, publicHeader, null);
    if (this.sessionToken != "") {
      publicHeader["x-cos-security-token"] = this.sessionToken;
    }

    HttpClientRequest request = await httpClient.putUrl(Uri.parse(requestUrl));

    Iterable<MapEntry<String, String>> entries = publicHeader.entries;
    for (MapEntry<String, String> entry in entries) {
      request.headers.add(entry.key, entry.value);
    }
    var file = new File(filePathName);
    var stream = file.openRead();
    await request.addStream(stream);
    var response = await request.close();
    if (response.statusCode == 200) {
      print('请求成功');
      return "请求成功";
    } else {
      print('请求失败');
      return "请求失败";
    }
  }

  //构建请求头
  Map<String, String> _buildSignHeaders(Map<String, String> headerMap) {
    Map<String, String> result = new Map();
    if (headerMap == null || headerMap.isEmpty) {
      return result;
    }

    Iterable<MapEntry<String, String>> entries = headerMap.entries;
    for (MapEntry<String, String> entry in entries) {
      if (entry.key.toLowerCase() == "content-type" ||
          entry.key.toLowerCase() == "content-md5" ||
          entry.key.startsWith("x")) {
        result[entry.key.toLowerCase()] = entry.value;
      }
    }

    return result;
  }

  //计算签名
  String getAuth(String httpMethod, String httpUrl, Map<String, String> headers,
      Map<String, String> params) {
    headers = headers ?? Map();
    params = params ?? Map();
    headers = _buildSignHeaders(headers);
    int currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
    var keyTime =
        '$currentTimestamp;${currentTimestamp + signExpireTimeInSeconds}';

    headers = headers.map((key, value) => MapEntry(key.toLowerCase(), value));
    params = params.map((key, value) => MapEntry(key.toLowerCase(), value));
    List<String> headerKeys = headers.keys.toList();
    headerKeys.sort();
    var headerList = headerKeys.join(';');
    var httpHeaders = headerKeys
        .map((item) => '$item=${Uri.encodeFull(headers[item])}')
        .join('&');

    List<String> paramKeys = params.keys.toList();
    paramKeys.sort();
    var urlParamList = paramKeys.join(';');
    var httpParameters = paramKeys
        .map((item) => '$item=${Uri.encodeFull(params[item])}')
        .join('&');

    var signKey =
        new Hmac(sha1, utf8.encode(tmpSecretKey)).convert(utf8.encode(keyTime));
    String httpString =
        '${httpMethod.toLowerCase()}\n$httpUrl\n$httpParameters\n$httpHeaders\n';
    var httpStringData = sha1.convert(utf8.encode(httpString));
    String stringToSign = 'sha1\n$keyTime\n$httpStringData\n';
    var signature = new Hmac(sha1, utf8.encode(signKey.toString()))
        .convert(utf8.encode(stringToSign));
    String auth =
        'q-sign-algorithm=sha1&q-ak=$tmpSecretId&q-sign-time=$keyTime&q-key-time=$keyTime&q-header-list=$headerList&q-url-param-list=$urlParamList&q-signature=$signature';
    return auth;
  }
}
